package main

import (
	"fmt"
	"golang.org/x/net/ipv4"
	"math/rand"
	"net"
	"syscall"
)

// 未被验证..
func main() {
	// 本地地址和目的地址，目前拓扑来看应该是同一个子网里面
	source, _ := net.ResolveIPAddr("ip4", "143.198.107.33")
	destination, _ := net.ResolveIPAddr("ip4", "143.198.107.33")

	// 创建raw socket
	// fd, error := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
	fd, error := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
	if error != nil {
		fmt.Print("创建raw socket错误" + error.Error())
		return
	}
	defer syscall.Close(fd)

	// 开启IP_HDRINCL
	error = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
	if error != nil {
		fmt.Println("开启IP_HDRINCL错误" + error.Error())
		return
	}

	// 应用层数据
	application := []byte("233")

	// 自定义传输层协议头
	//transmission := []byte{
	//	uint8(12345 >> 8), uint8(12345 % 256),    // 源端口
	//	uint8(54321 >> 8), uint8(54321 % 256),    // 目的端口
	//}
	// 自定义传输层协议
	sourcePort := rand.Uint32()
	transmission := []byte{
		uint8(sourcePort >> 8), uint8(sourcePort % 256), // 源端口
		uint8(54321 >> 8), uint8(54321 % 256), // 目的端口
		uint8(0), uint8(len(application) + 12), // 长度（应用层数据长度+传输层报头长度）
		uint8(0), uint8(0), // 校验和，先填0，一会计算
		uint8(0), uint8(1), // 报文序号1
		uint8(0), uint8(0), // ACK序号0
	}

	// 计算校验和
	checksum := udpCheckSum(
		source.IP.To4(),
		destination.IP.To4(),
		len(application)+12,
		append(transmission, application...))

	transmission[6] = uint8(checksum >> 8)
	transmission[7] = uint8(checksum % 256)

	// IP头，重要的是协议字段写233号协议
	ipHeader := ipv4.Header{
		Version:  4,
		Len:      20,
		TotalLen: len(application) + len(transmission) + 20,
		TTL:      64,
		Protocol: 17, // udp raw socket默认端口号是17..
		Dst:      destination.IP.To4(),
		Src:      source.IP.To4(),
		// Flags: ipv4.DontFragment,
	}
	// 生成字节串
	ip, _ := ipHeader.Marshal()

	// 组装报文
	payload := append(ip, transmission...)
	payload = append(payload, application...)

	// 通过raw socket发送报文
	destinationAddress := syscall.SockaddrInet4{
		Port: 0,
		Addr: [4]byte{
			destination.IP.To4()[0],
			destination.IP.To4()[1],
			destination.IP.To4()[2],
			destination.IP.To4()[3],
		},
	}
	error = syscall.Sendto(fd, payload, 0, &destinationAddress)
	if error != nil {
		fmt.Println("发送错误" + error.Error())
		return
	}

	fmt.Println("发送成功")
}

func udpCheckSum(source []byte, destination []byte, udpLength int, udp []byte) uint16 {
	// 创建伪首部
	header := []byte{
		source[0], source[1], source[2], source[3],
		destination[0], destination[1], destination[2], destination[3],
		0, 17, 0, uint8(udpLength),
	}

	total := append(header, udp...)

	// 计算校验和
	var (
		sum    uint32
		length int = len(total)
		index  int
	)
	//以每16位为单位进行求和，直到所有的字节全部求完或者只剩下一个8位字节（如果剩余一个8位字节说明字节数为奇数个）
	for length > 1 {
		sum += uint32(total[index])<<8 + uint32(total[index+1])
		index += 2
		length -= 2
	}
	//如果字节数为奇数个，要加上最后剩下的那个8位字节
	if length > 0 {
		sum += uint32(total[index])
	}
	//加上高16位进位的部分
	sum += (sum >> 16)
	//别忘了返回的时候先求反
	return uint16(^sum)
}
